/*
 * Print user IDs.
 *
 * Copyright 2022 and 2023 Odin Kroeger.
 *
 * This file is part of suCGI.
 *
 * suCGI is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * suCGI is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
 * Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with suCGI. If not, see <https://www.gnu.org/licenses/>.
 */

#define _BSD_SOURCE
#define _DARWIN_C_SOURCE
#define _DEFAULT_SOURCE
#define _GNU_SOURCE

#include <sys/types.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <pwd.h>
#include <inttypes.h>
#include <search.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


/*
 * Constants
 */

/* Exit status for usage errors */
#define EXIT_USAGE 2

/* Number of seen IDs that memory should be reserved for initially */
#define NIDS 2048


/*
 * Prototypes
 */

/* Compare two user IDs. */
static int cmpuids(const uid_t *uid1, const uid_t *uid2);


/*
 * Main
 */

int
main(int argc, char **argv)
{
    int opt;
    /* RATS: ignore; uids is not security-sensitive */
    while ((opt = getopt(argc, argv, "gh")) != -1) {
        switch (opt) {
        case 'h':
            (void) puts(
"uids - print user IDs and names\n\n"
"Usage:  uids\n"
"        uids -h\n\n"
"Options:\n"
"    -h  Print this help screen.\n\n"
"Copyright 2022 and 2023 Odin Kroeger.\n"
"Released under the GNU Affero General Public License.\n"
"This programme comes with ABSOLUTELY NO WARRANTY."
            );
            return EXIT_SUCCESS;
        default:
            return EXIT_USAGE;
        }
    }

    argc -= optind;

    if (argc > 0) {
        (void) fputs("usage: uids\n", stderr);
        return EXIT_USAGE;
    }

    size_t maxnseen = NIDS;
    /* cppcheck-suppress misra-c2012-11.5; bad advice for calloc */
    uid_t *seen = calloc(maxnseen, sizeof(*seen));
    if (!seen) {
        err(EXIT_FAILURE, "malloc");
    }

    errno = 0;
    setpwent();
    if (errno != 0) {
        err(EXIT_FAILURE, "setpwsent");
    }

    size_t nseen = 0;
    const struct passwd *pwd;
    /* cppcheck-suppress getpwentCalled; used portably and safely */
    while ((errno = 0, pwd = getpwent()) != NULL) {
        const uid_t uid = pwd->pw_uid;
        /* cppcheck-suppress misra-c2012-11.5; conversion is safe */
        const uid_t *dup = lfind(&uid, seen, &nseen, sizeof(*seen),
                                 (int (*)(const void *, const void *)) cmpuids);

        if (!dup) {
            const char *const name = pwd->pw_name;

            if ((uid_t) -1 < (uid_t) 1) {
                (void) printf("%lld %s\n", (long long) uid, name);
            } else {
                (void) printf("%llu %s\n", (unsigned long long) uid, name);
            }

            if (nseen == maxnseen) {
                if (sizeof(maxnseen) / maxnseen < 2U) {
                    errx(EXIT_FAILURE, "Too many users");
                }
                maxnseen *= 2;

                if (SIZE_MAX / sizeof(*seen) > maxnseen) {
                    errx(EXIT_FAILURE, "Too many users");
                }

                /* NOLINTBEGIN(bugprone-suspicious-realloc-usage) */

                /* cppcheck-suppress misra-c2012-11.5; bad advice for realloc */
                seen = realloc(seen, maxnseen * sizeof(*seen)); /* RATS: ignore */
                if (!seen) {
                    err(EXIT_FAILURE, "realloc");
                }

                /* NOLINTEND(bugprone-suspicious-realloc-usage) */
            }

            seen[nseen] = uid;
            ++nseen;
        }
    }

    /* Superfluous, but some versions of ASan think otherwise. */
    free(seen);

    if (errno != 0) {
        err(EXIT_FAILURE, "getpwsent");
    }

    errno = 0;
    endpwent();
    if (errno != 0) {
        err(EXIT_FAILURE, "endpwent");
    }

    return EXIT_SUCCESS;
}


/*
 * Functions
 */

static int
cmpuids(const uid_t *const uid1, const uid_t *const uid2)
{
    assert(uid1 != NULL);
    assert(uid2 != NULL);

    if (*uid1 < *uid2) {
        return -1;
    };

    if (*uid1 > *uid2) {
        return 1;
    }

    return 0;
}
